package com.lcykcl.ttgen;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

public class TimeTable {

	private ArrayList<Node> bestConflict;
	private int requiredLen;
	private Persona persona;

	private HashMap<String, ArrayList<Node>> originalSiblings;
	// private HashMap<String, ArrayList<Node>> lectureNodes;

	private HashMap<String, ArrayList<Node>> updatedSiblings;
	// private HashMap<String, ArrayList<Node>> siblings;

	// List of combinations of year sections
	private ArrayList<ArrayList<Node>> yearSections;

	private ArrayList<Node> fixedNodes;
	private ArrayList<Node> timeTable;
        public ArrayList<ArrayList<Node>> allTimetables;

	private boolean isTimetableGernerated;

	public TimeTable(ArrayList<ArrayList<Node>> yearSections,
			HashMap<String, ArrayList<Node>> originalSiblings,
			ArrayList<Node> fixedNodes, Persona p) {
		
		// initiation of instance variables
		bestConflict = new ArrayList<Node>();
		requiredLen = originalSiblings.values().size();
		persona = p;
		this.originalSiblings = originalSiblings;
		this.updatedSiblings = new HashMap<String, ArrayList<Node>>();
		this.yearSections = yearSections;
		this.fixedNodes = fixedNodes;
		timeTable = new ArrayList<Node>();
		isTimetableGernerated = false;
		updatedSiblings = (HashMap<String, ArrayList<Node>>) originalSiblings.clone();
	}


	//TimeTable Generator in the old version
	//It is expected to work similar to the orginal version
	//except for setting isTimetableGenerated as true
	//if a completed time table is generated
	/** uses startNode and its siblings
	uses generatePath
	returns a list of all timetables generated for startNode and its sibling Nodes */
	public  void generateTTB() {
		
		//ArrayList<ArrayList<Node>> allTimetables = new ArrayList<Node>;
		ArrayList<Node> empty = new ArrayList<Node>;
		
		//set value to the updatedSiblings 
		setUpdatedSiblings();
		
		//find the start Node
		//Node startNode = getStartNode();
		//TODO: getStartNode should return the String name of startNode, not the Node startNode.
		String startNodeName = getStartNode();
		
		//get the list of startNode and its siblings from updatedSiblings
		ArrayList<Node> startNodeList = updatedSiblings.get(startNodeName);
		
		for (Node startNode : startNodeList){
			
			//set Edges for the start Node
			setEdges(startNode);
			
			ArrayList<Node> timetable = generatePath(startNode, startNode.edges, empty);
			if (timetable != null)
				allTimetables.add(timetable);
		}
                //TODO: if we are done with generating all timetables, and allTimetables is empty,
                // that means we didn't successfully generate any timetable. Then use originalSiblings 
                // to update bestConflict (fill up bestConflict to the requiredLen. ANY time is fine 
                //for the remaining courses not yet in bestConflict.)
		//return allTimetables;
	}
	
	/**
	 * Returns the intersection of two lists of Nodes, i.e., returns all the nodes that are in common in list1 and list2.
	 * This method shouldn't change the order of the sorted edges.
	 */
	private ArrayList<Node> intersect(ArrayList<Node> prevEnNodeList, ArrayList<Node> startNodeEdgesList){
		
		if (prevEnNodeList != startNodeEdgesList){
			
			// create a new copy of startNodeEdgesList because retainAll changes the Collection itself, 
			// which means if we use startNodeEdgesList directly, startNode's edge list will be modified. 
			ArrayList<Node> startNodeEdgesClone = new ArrayList<Node>(startNodeEdgesList);
			
			startNodeEdgesClone.retainAll(prevEnNodeList);
			return startNodeEdgesClone;
		}
		else return startNodeEdgesList;
	}
	
	
	/** 
	This method either updates the self.bestConflict while returning null, or returns a successful timetable
	for the startNode specified.
	*/
	private ArrayList<Node> generatePath(Node startNode, ArrayList<Node> prevEn, ArrayList<Node> selected) {
		
		ArrayList<Node> currentEn = intersect(prevEn, startNode.edges);
		
		// instead of creating a new list currentSelected as in the old version, here we just append
		// startNode to the ArrayList selected
		selected.add(startNode);
		
		int i = 0;
		while ( i < currentEn.size() && selected.size() <  this.requiredLen){
			
			ArrayList<Node> iterList = generatePath(currentEn.get(i), currentEn, selected);
			if (iterList != null){
				selected = iterList;
			}
			i++;
		}
		
		if (selected.size().equals(this.requiredLen)){
			return selected;
		}
		else {
			setBestConflict(selected);
			return null;
		}
	}
	
	
	//getBestConflict in the old version
	//I just changed the name because what we do is actually 
	//setting the BestConflict 
	private void setBestConflict(ArrayList<Node> conflictList) {
		
		if (this.bestConflict.size()) < conflictList.size()){
		    this.bestConflict = conflictList;
		}
	}
	
	
	private void setUpdatedSiblings() {
	//set value to the updatedSiblings based on the information of fixed nodes, year sections and startNode(if year section is empty)
	//Node = getStartNode();	
		if (this.yearSections.size() == 0) {
			Node start = getStartNode();
			fixNode(start);
		}else{
			ArrayList<Node> fixedYears = this.yearSections.get(0);
			for (int i = 0; i < fixedYears.size(); i++) {
				fixNode(fixedYears.get(i));
			}
		}
		
		Iterator<String> iter = this.updatedSiblings.keySet().iterator();
		while (iter.hasNext()) {
			String key = iter.next();
			int sizeOfFixed = this.fixedNodes.size(); 
			//if the key is not the Node's code then 
			ArrayList<Node> value = this.updatedSiblings.get(key);
			for (int i = 0; i < value.size(); i++) {
				Node nodeTocheck = value.get(i);
				for (int j = 0; j < sizeOfFixed; j++) {
					if (nodeTocheck.isConflict(this.fixedNodes.get(i))){
						this.updatedSiblings.get(key).remove(i);
					}
				}
			}
		}
	}
	
        /**
         * removes all the siblings of Node toFix in updatedSiblings Hashmap. 
         * @param toFix 
         */
	private void fixNode(Node toFix) {
		ArrayList<Node> fixed = new ArrayList<Node>();
		fixed.add(toFix);
		String key = toFix.getCode();
		this.updatedSiblings.remove(key);
		this.updatedSiblings.put(key, fixed);
	}

        /**
         * returns the startNode, which is the first Node of the the first combination of yearSections
         * (which is an ArrayList) if there is a Y section. If there's no Y section, then return the 
         * startNode from the updatedSiblings HashMap.
         * @return 
         */
	private Node getStartNode() {
		//return the Start Node based on the information from 
		//the updatedSiblings
		if (this.yearSections.size() > 0) {
			return this.yearSections.get(0).get(0);
		}else if(this.updatedSiblings.values().size() > 0){
			String smallestKey = getSmallestKey();
			return this.updatedSiblings.get(smallestKey).get(0);
		}else{
			//if there are no node to generate the time table them it should return null;
			return null;
		}
	}
	
        /**
         * returns the String key that has the shortest length of siblings from the updatedSiblings map.
         * @return 
         */
	private String getSmallestKey() {
		int smallest = 100;
		String smallestKey = "";
		Iterator<String> iter = this.updatedSiblings.keySet().iterator();
		while (iter.hasNext()) {
			String key = iter.next();
			int size = this.updatedSiblings.get(key).size();
			if (smallest > size) {
				smallest = size;
				smallestKey = key;
			}
		}
		return smallestKey;
	}

        /**
         * set edges for one Node, i.e. make the graph.
         * @param start 
         */
	private void setEdges(Node start) {
		//setEdges from the start Node 
		Iterator<String> iter = this.updatedSiblings.keySet().iterator();
		ArrayList<Node> nodesToConnect = new ArrayList<Node> ();
		while (iter.hasNext()) {
			String key = iter.next();
			//if the key is not the Node's code then 
			if (key.equalsIgnoreCase(start.getCode())) {
				nodesToConnect.addAll(this.updatedSiblings.get(key));
			}
		}
		int numOfNodeToconnect = nodesToConnect.size();
		while (numOfNodeToconnect > 1) {
			Node nodeToConnect1 = nodesToConnect.remove(0);
			for (int i = 0; i < nodesToConnect.size(); i++) {
				Node nodeToConnect2 = nodesToConnect.get(i);
				nodeToConnect1.addEdge(nodeToConnect2);
				nodeToConnect2.addEdge(nodeToConnect1);
			}
			numOfNodeToconnect -= 1;
		}

	}


	public boolean isWorkComplete() {
		// it is true iff if finds completed timetable or it runs out of the
		// startNode combination
		// TODO Auto-generated method stub
		return yearSections.isEmpty() || isTimetableGernerated;
	}

        /**
         * removes the index 0 ArrayList from the combinations of year sections.
         */
	public void tryAgain() {
		// TODO Auto-generated method stub
		yearSections.remove(0);
		generateTTB();
	}

        
	public ArrayList<Node> getTimeTable() {
		// TODO Auto-generated method stub
		// if it's conflict then we just return the conflict timetable.
		return this.allTimetables;
	}

	public boolean isConflict() {
		// TODO Auto-generated method stub
		return !isTimetableGernerated;
	}

	public boolean isPossibletoProceed() {
		// TODO Auto-generated method stub
		// the yearSections is not Empty
		return !yearSections.isEmpty();
	}


	public void updateYearSections() {
		//remove the first element in the yearSections
		if (this.yearSections.size() > 0) {
			this.yearSections.remove(0);
		}
	}

	public void updateFixed(ArrayList<Node> fixed) {
		//update fixedNodes based on the information from the outside 
		//of this class
		fixedNodes = fixed;
	}


	public void updatePersona(Persona p) {
		//update Persona
		this.persona = p;
	}

}
